#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <malloc.h>
#include <alloca.h>
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>

#include "cfd-mmap.h"

struct
mfile * mmap_file(const char * file_path
                 , long file_size
                 , int  flags)
{
    int fd = -1;

    long   memory_size = file_size;
    long   file_path_dup_size = strlen(file_path) + 6 + 1;
    char * file_path_dup = alloca(file_path_dup_size);
    memcpy(file_path_dup, file_path, strlen(file_path)+1);

    if (file_size > 0)
    {
        /* Ok. Create template file with specified prefix */
        file_path_dup[strlen(file_path)] = 0;
        snprintf(file_path_dup
                 , file_path_dup_size
                 , "%sXXXXXX"
                 , file_path
                 );

        fd = mkostemps(file_path_dup
                       , 0
                       , flags
                       );
    }
    else
    {
        /* Just open file */
        fd = open(file_path_dup
                  , flags
                  );
    }

    if (fd == -1)
    {
        fprintf(stderr
                , "Couldn't %s file %s: %s\n"
                , file_size == 0
                  ? "create"
                  : "open"
                , file_path_dup
                , strerror(errno)
                );

        return NULL;
    }

    if (file_size > 0)
    {
        ftruncate(fd, file_size);
        memory_size = file_size;
    }
    else
    {
        struct stat _stat;
        fstat(fd, &_stat);
        memory_size = _stat.st_size;
    }

    int prot_flags = PROT_READ;

    prot_flags |= (flags & O_WRONLY)
                  ? PROT_WRITE
                  : 0
                  ;

    void * memory = mmap(0
                         , memory_size
                         , prot_flags
                         , MAP_PRIVATE
                         , fd
                         , 0
                         );

    if (NULL == memory)
    {
        fprintf(stderr
                , "Couldn't mmap file area for %s file %s: %s\n"
                , file_size > 0
                  ? "existed"
                  : "new"
                , file_path_dup
                , strerror(errno)
                );

        goto lbBad;
    }

    struct mfile * mfile = malloc(sizeof(*mfile));

    if (NULL == mfile)
    {
        fprintf(stderr
                , "Couldn't allocate mmaped file container: %s\n"
                , strerror(errno)
                );

        munmap(memory, memory_size);
        goto lbBad;
    }

    mfile->memory      = memory;
    mfile->memory_size = memory_size;
    mfile->__fd        = fd;
    mfile->__temporary = file_size > 0;
    mfile->__path      = strdup(file_path_dup);

    return mfile;

  lbBad:
    close(fd);

    if (file_size > 0)
        unlink(file_path_dup);

    return NULL;

}

void munmap_file(struct mfile * mfile)
{
    if (NULL == mfile)
        return;

    munmap(mfile->memory, mfile->memory_size);
    close(mfile->__fd);

    if (mfile->__temporary)
        unlink(mfile->__path);

    free(mfile->__path);
    free(mfile);
}
